package main

import (
	"bytes"
	"context"
	"errors"
	"gitee.com/kaylee595/gkaylee"
	"github.com/google/uuid"
	"github.com/kaylee595/tracerr"
	"github.com/samber/lo"
	"io"
	"io/fs"
	"log/slog"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
	"videoPlay/internal/dal"
	"videoPlay/internal/dal/dalModels"
	"videoPlay/internal/services/video"
)

func init() {
	dal.DB = dal.ConnectDB("sqlite.db")
}

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	srcDir := `D:\awesome\videoDownloader\downloads\`
	savePreviewImgDir := `E:\videoPlay\hls\preview\`
	saveVideoDir := `E:\videoPlay\video\`

	entries, err := os.ReadDir(srcDir)
	if err != nil {
		panic(err)
	}
	handle := handler(ctx, savePreviewImgDir, saveVideoDir)
	for _, entry := range entries {
		err = handle(srcDir+entry.Name(), entry, nil)
		if err != nil {
			panic(err)
		}
	}
}

// filenameExtIsVideo 检查文件后缀名是否为视频格式
func filenameExtIsVideo(filename string) bool {
	ext := filepath.Ext(filename)
	extList := [...]string{".mp4", ".avi"}
	for _, e := range extList {
		if strings.EqualFold(ext, e) {
			return true
		}
	}
	return false
}

// generatePreviewImgFromVideo 从视频生成预览图
func generatePreviewImgFromVideo(ctx context.Context, videoFilename, previewImgFilename string) error {
	// ffmpeg -i videoFilename -vf select=eq(n\,980),scale=-1:'min(480,ih)' -vframes 1 -y previewImgFilename
	cmd := exec.CommandContext(ctx, "ffmpeg", "-i", videoFilename, "-vf", "select=eq(n\\,980),scale=-1:'min(480,ih)'", "-vframes", "1", "-y", previewImgFilename)
	output := &bytes.Buffer{}
	cmd.Stdout = output
	cmd.Stderr = output
	err := cmd.Run()
	if err != nil {
		return tracerr.Wrap(err)
	}
	return nil
}

// copyFile 复制文件
func copyFile(ctx context.Context, src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return tracerr.Wrap(err)
	}
	defer srcFile.Close()
	dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700)
	if err != nil {
		return tracerr.Wrap(err)
	}
	defer func() {
		if err != nil {
			// 如果Copy出错了, 删除掉文件
			_ = os.Remove(dst)
		}
	}()
	defer dstFile.Close()
	buf := make([]byte, 512*1024*1024)
	for {
		select {
		case <-ctx.Done():
			return tracerr.Wrap(ctx.Err())
		default:
		}
		var n int
		n, err = srcFile.Read(buf)
		if n > 0 {
			_, err2 := dstFile.Write(buf[:n])
			if err2 != nil {
				err = err2
				return tracerr.Wrap(err)
			}
		}
		if err != nil {
			if errors.Is(err, io.EOF) {
				err = nil
				break
			}
			return tracerr.Wrap(err)
		}
	}
	return nil
}

// handler 视频文件处理
func handler(ctx context.Context, savePreviewImgDir, saveVideoDir string) fs.WalkDirFunc {
	service := video.New(dal.DB)
	return func(filename string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() || !filenameExtIsVideo(filename) { // 跳过文件夹和非视频文件
			return nil
		}
		// 检查文件名是否已经在数据库中存储过
		onlyName := filepath.Base(filename)
		onlyName = strings.TrimSuffix(onlyName, filepath.Ext(onlyName)) //去除后缀
		videoList, err := service.GetVideoList("", nil)
		if err != nil {
			return tracerr.Wrap(err)
		}
		_, ok := lo.Find(videoList, func(v *dalModels.Video) bool {
			return v.Name == onlyName || v.Id.String() == onlyName
		})
		if ok {
			slog.Warn("视频文件视乎已经存在数据库中, 已阻止重复添加", "filename", filename)
			return nil
		}
		videoId := uuid.New()
		// 确定视频文件转存位置以及预览图存放位置
		_ = os.MkdirAll(savePreviewImgDir, 0600) // 确保输出的文件路径存在
		previewImgFilename := filepath.Join(savePreviewImgDir, videoId.String()+".png")
		_ = os.MkdirAll(saveVideoDir, 0600) // 确保转存位置路径存在
		newVideoFilename := filepath.Join(saveVideoDir, videoId.String()+filepath.Ext(filename))
		// 添加记录
		var success bool
		_, err = service.AddVideo(videoId, time.Now(), onlyName, newVideoFilename, previewImgFilename)
		if err != nil {
			return tracerr.Wrap(err)
		}
		defer func() { // 后续流程出错了, 应该删除记录
			if !success {
				_ = service.DeleteVideoByVideoId(videoId, true)
			}
		}()
		// 生成视频预览图
		err = generatePreviewImgFromVideo(ctx, filename, previewImgFilename)
		if err != nil {
			return tracerr.Wrap(err)
		}
		defer func() { // 后续流程出错了, 应该删除预览图
			if !success {
				gkaylee.IgnoreErr0(os.Remove(previewImgFilename))
			}
		}()
		// 转存视频路径
		err = copyFile(ctx, filename, newVideoFilename)
		if err != nil {
			return tracerr.Wrap(err)
		}
		// 删除原视频
		_ = os.Remove(filename)
		success = true
		return nil
	}
}
